﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using InvariantWM.Log;
using System.Collections;

namespace InvariantWM.NormalizerUtils
{
    static class Image2DTransform
    {
        static int count = 0;
        /// <summary>
        /// Escalado de una imagen
        /// </summary>
        /// <param name="Bitmap"></param>
        /// <param name="ScaleFactorX"></param>
        /// <param name="ScaleFactorY"></param>
        /// <returns></returns>
        public static Bitmap scaleImage(Bitmap Bitmap, float ScaleFactorX, float ScaleFactorY)
        {
            int scaleWidth = (int)Math.Max(Bitmap.Width * ScaleFactorX, 1.0f);
            int scaleHeight = (int)Math.Max(Bitmap.Height * ScaleFactorY, 1.0f);

            Bitmap scaledBitmap = new Bitmap(scaleWidth, scaleHeight, PixelFormat.Format24bppRgb);

            // Scale the bitmap in high quality mode.
            using (Graphics gr = Graphics.FromImage(scaledBitmap))
            {
                gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                gr.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                gr.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                gr.DrawImage(Bitmap, new Rectangle(0, 0, scaleWidth, scaleHeight), new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), GraphicsUnit.Pixel);
            }

            // Copy original Bitmap's EXIF tags to new bitmap.
            foreach (PropertyItem propertyItem in Bitmap.PropertyItems)
            {
                scaledBitmap.SetPropertyItem(propertyItem);
            }

            return scaledBitmap;
        }

        public static Bitmap scaleImage(Bitmap Bitmap, int scaleWidth, int scaleHeight)
        {
            

            Bitmap scaledBitmap = new Bitmap(scaleWidth, scaleHeight, PixelFormat.Format24bppRgb);

            // Scale the bitmap in high quality mode.
            using (Graphics gr = Graphics.FromImage(scaledBitmap))
            {
                gr.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                gr.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
                gr.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
                gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                gr.DrawImage(Bitmap, new Rectangle(0, 0, scaleWidth, scaleHeight), new Rectangle(0, 0, Bitmap.Width, Bitmap.Height), GraphicsUnit.Pixel);
            }

            // Copy original Bitmap's EXIF tags to new bitmap.
            foreach (PropertyItem propertyItem in Bitmap.PropertyItems)
            {
                scaledBitmap.SetPropertyItem(propertyItem);
            }

            return scaledBitmap;
        }

        /// <summary>
        /// Rotado de la imagen en una imagen del mismo tamaño, en un parche circular inscrito dentro de un cuadrado
        /// </summary>
        /// <param name="inBitmap"></param>
        /// <param name="angle"></param>
        /// <returns></returns>
        public static Bitmap rotateImage(Bitmap inBitmap, double angle, Point2D center)
        {
            //Parece ser que aforge implementa ya una forma de transformaciones afines...
            //la función en particular toma como parametros a,b,c,d,e,f, que son dentro de la matriz de transformacion
            //2D los siguientes coeficientes:
            //((a, b, 0, c),
            // (d, e, 0, f),
            // (0, 0, 1, 0),
            // (0, 0, 0, 1))

            //defino una transformación con la rotación inversa y una traslación al origen...
            double cos = Math.Cos(-angle);
            double sin = Math.Sin(-angle);

            double centerx = center.getX();
                //inBitmap.Width / 2.0;
            double centery = center.getY();
                //inBitmap.Height / 2.0;

            // <summary>
            // Initializes a new instance of the <see cref="AffineTransformation2D"/> class
            // </summary>
            // 
            // <param name="a">[0, 0] element of transformation matrix</param>
            // <param name="b">[0, 1] element of transformation matrix</param>
            // <param name="c">[1, 0] element of transformation matrix</param>
            // <param name="d">[1, 1] element of transformation matrix</param>
            // <param name="e">[0] element of move vector</param>
            // <param name="f">[1] element of move vector</param>
            // 
            Transform2D moveToOrigin;
            Transform2D reverse2DRotation;
            Transform2D backFromOrigin;


            Bitmap rotatedBitmap = new Bitmap(inBitmap.Width, inBitmap.Height);
            double finalx, finaly;
            Point2D paux1, paux2, paux3, paux4;
            Point2D offset = getOffset(inBitmap, angle, center);

            for (int i = 0; i < rotatedBitmap.Width; i++)
            {
                for (int j = 0; j < rotatedBitmap.Height; j++)
                {
                    finalx = i;
                    finaly = j;
                    paux1 = new Point2D(finalx, finaly);

                    moveToOrigin = new Transform2D(1, 0, 0, 1, -centerx, -centery);
                    paux2 = moveToOrigin.Transform(paux1);

                    reverse2DRotation = new Transform2D(cos, -sin, sin, cos, 0, 0);
                    paux3 = reverse2DRotation.Transform(paux2);

                    backFromOrigin = new Transform2D(1, 0, 0, 1, centerx - offset.getX(), centery - offset.getY());
                    paux4 = backFromOrigin.Transform(paux3);
                    //ahora ya tengo en originalx, originaly las coordenadas trasnformadas del punto en la primera
                    //imagen, lo que puedo hacer ahora es interpolar el valor de dicho punto respecto a los que
                    //están alrededor...
                    //rotatedBitmap.SetPixel(i, j, colorAverage(inBitmap, paux4.getX(), paux4.getY()));
                    if (paux4.getX() > 0 && paux4.getY() > 0 && paux4.getX() < inBitmap.Width && paux4.getY()< inBitmap.Height)
                    {
                        //rotatedBitmap.SetPixel((int)(paux4.getX() - offset.getX()), (int)(paux4.getY() - offset.getY()), colorAverage(inBitmap, i, j));
                        rotatedBitmap.SetPixel(i,j,colorAverage(inBitmap,paux4.getX(),paux4.getY()));
                    }
                    else {
                        rotatedBitmap.SetPixel(i, j, Color.Black);
                    }               
                }
            }
            rotatedBitmap.Save("rotated" + count++ + ".bmp");
            return rotatedBitmap;
        }

        public static Bitmap rotateImage2(Bitmap inBitmap, double angle, Point2D center)
        {
            //Parece ser que aforge implementa ya una forma de transformaciones afines...
            //la función en particular toma como parametros a,b,c,d,e,f, que son dentro de la matriz de transformacion
            //2D los siguientes coeficientes:
            //((a, b, 0, c),
            // (d, e, 0, f),
            // (0, 0, 1, 0),
            // (0, 0, 0, 1))

            //defino una transformación con la rotación inversa y una traslación al origen...
            double cos = Math.Cos(-angle);
            double sin = Math.Sin(-angle);

            double centerx = center.getX();
            //inBitmap.Width / 2.0;
            double centery = center.getY();
            //inBitmap.Height / 2.0;

            // <summary>
            // Initializes a new instance of the <see cref="AffineTransformation2D"/> class
            // </summary>
            // 
            // <param name="a">[0, 0] element of transformation matrix</param>
            // <param name="b">[0, 1] element of transformation matrix</param>
            // <param name="c">[1, 0] element of transformation matrix</param>
            // <param name="d">[1, 1] element of transformation matrix</param>
            // <param name="e">[0] element of move vector</param>
            // <param name="f">[1] element of move vector</param>
            // 


            Bitmap rotatedBitmap = new Bitmap(inBitmap.Width, inBitmap.Height);

            Point2D paux;
            //Sin AA{
            //Point2D paux2;
            //}

            Point2D offset = getOffset(inBitmap, angle, center);

            for (int i = 0; i < rotatedBitmap.Width; i++)
            {
                for (int j = 0; j < rotatedBitmap.Height; j++)
                {
                   // finalx = i;
                   //finaly = j;
                    paux = new Point2D(i, j);

                    //SIN AA{
                    //paux2 = Point2D.Point2DRotate(paux, angle, center);
                    //paux2 = Point2D.detranslate(paux2,offset);
                    //paux = paux2;
                    //}

                    //ahora ya tengo en originalx, originaly las coordenadas trasnformadas del punto en la primera
                    //imagen, lo que puedo hacer ahora es interpolar el valor de dicho punto respecto a los que
                    //están alrededor...
                    //rotatedBitmap.SetPixel(i, j, colorAverage(inBitmap, paux4.getX(), paux4.getY()));
                    if (paux.getX() > 0 && paux.getY() > 0 && paux.getX() < inBitmap.Width && paux.getY() < inBitmap.Height)
                    {
                        
                        rotatedBitmap.SetPixel(i, j, colorAverageAntiAliased(inBitmap, angle, paux, center, offset));
                        //Sin AA{
                        //rotatedBitmap.SetPixel(i, j,colorAverage(inBitmap, paux));
                        //}
                    }
                    else
                    {
                        rotatedBitmap.SetPixel(i, j, Color.Black);
                    }
                }
            }
            rotatedBitmap.Save("rotated" + count++ + ".bmp");
            return rotatedBitmap;
        }

       
        /// <summary>
        /// Interpolación bilineal del color de un punto x, y de la imagen, que no se tienen porque corresponder
        /// con un pixel exacto.
        /// </summary>
        /// <param name="inBitmap"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public static Color colorAverage(Bitmap inBitmap, double x, double y)
        {
            if ((x <= 0 || x >= inBitmap.Width - 1) || (y <= 0 || y >= inBitmap.Height - 1))
            {
                return Color.Black;
            }
            else
            {
                int xleft, xright, yup, ydown;


                xleft = (int)Math.Floor(x);
                xright = xleft + 1;
                yup = (int)Math.Floor(y);
                ydown = yup + 1;

                //media de los superiores
                byte r1 = (byte)(inBitmap.GetPixel(xleft, yup).R * (Math.Abs(xleft - x)) +
                                 inBitmap.GetPixel(xright, yup).R * (Math.Abs(xright - x)));
                byte g1 = (byte)(inBitmap.GetPixel(xleft, yup).G * (Math.Abs(xleft - x)) +
                                  inBitmap.GetPixel(xright, yup).G * (Math.Abs(xright - x)));
                byte b1 = (byte)(inBitmap.GetPixel(xleft, yup).B * (Math.Abs(xleft - x)) +
                                 inBitmap.GetPixel(xright, yup).B * (Math.Abs(xright - x)));
                //media de los inferiores

                byte r2 = (byte)(inBitmap.GetPixel(xleft, ydown).R * (Math.Abs(xleft - x)) +
                               inBitmap.GetPixel(xright, ydown).R * (Math.Abs(xright - x)));
                byte g2 = (byte)(inBitmap.GetPixel(xleft, ydown).G * (Math.Abs(xleft - x)) +
                                  inBitmap.GetPixel(xright, ydown).G * (Math.Abs(xright - x)));
                byte b2 = (byte)(inBitmap.GetPixel(xleft, ydown).B * (Math.Abs(xleft - x)) +
                                 inBitmap.GetPixel(xright, ydown).B * (Math.Abs(xright - x)));

                //media de los dos colores anteriores.
                byte r = (byte)(r1 * (Math.Abs(yup - y)) +
                                 r2 * (Math.Abs(ydown - y)));
                byte g = (byte)(g1 * (Math.Abs(yup - y)) +
                                 g2 * (Math.Abs(ydown - y)));
                byte b = (byte)(b1 * (Math.Abs(yup - y)) +
                                 b2 * (Math.Abs(ydown - y)));

                return Color.FromArgb(255, r, g, b);
            }

        }

        public static Color colorAverage(Bitmap inBitmap, Point2D p) {
            return colorAverage(inBitmap, p.getX(), p.getY());
        }
        public static Point2D getRotatedSize(Bitmap inBitmap, double angle, Point2D center){
            Point2D esquinaSI=new Point2D(0,0),
                    esquinaSD=new Point2D(inBitmap.Size.Width-1,0), 
                    esquinaII=new Point2D(0,inBitmap.Size.Height-1),
                    esquinaID=new Point2D(inBitmap.Size.Width-1,inBitmap.Size.Height-1);
            //calculo sus rotaciones
            esquinaSI = Point2D.Point2DRotate(esquinaSI, angle, center);
            esquinaSD = Point2D.Point2DRotate(esquinaSD, angle, center);
            esquinaII = Point2D.Point2DRotate(esquinaII, angle, center);
            esquinaID = Point2D.Point2DRotate(esquinaID, angle, center);
            //minima x
            double minx, miny, maxx, maxy;

            minx =  Math.Min(esquinaSI.getX(), esquinaSD.getX());
            minx = Math.Min(minx, esquinaII.getX());
            minx = Math.Min(minx, esquinaID.getX());

            miny = Math.Min(esquinaSI.getY(), esquinaSD.getY());
            miny = Math.Min(miny, esquinaII.getY());
            miny = Math.Min(miny, esquinaID.getY());

            maxx = Math.Max(esquinaSI.getX(), esquinaSD.getX());
            maxx = Math.Max(maxx, esquinaII.getX());
            maxx = Math.Max(maxx, esquinaID.getX());

            maxy = Math.Max(esquinaSI.getY(), esquinaSD.getY());
            maxy = Math.Max(maxy, esquinaII.getY());
            maxy = Math.Max(maxy, esquinaID.getY());
           
            return new Point2D(Math.Abs(maxx-minx),Math.Abs(maxy-miny));
        }

        public static Point2D getOffset(Bitmap inBitmap, double angle, Point2D center)
        {

            Point2D geoCenter = new Point2D(inBitmap.Size.Width / 2.0F, inBitmap.Size.Height / 2.0F);
                   
            //calculo sus rotaciones
           geoCenter = Point2D.Point2DRotate(geoCenter, angle, center);
            
           return new Point2D(geoCenter.getX() - inBitmap.Width / 2.0F, geoCenter.getY() - inBitmap.Height / 2.0F);
                //Point2D.Point2DRotate(new Point2D(inBitmap.Width / 2.0F, inBitmap.Height / 2.0F), angle, center); ;
        }

        public static Bitmap createPatch(Bitmap inBitmap) {

            Point2D center = new Point2D(inBitmap.Width / 2.0F, inBitmap.Height / 2.0F);
            double radious = Math.Min(center.getX(),center.getY());
            Bitmap outBitmap = new Bitmap(inBitmap.Width,inBitmap.Height);
      

            for (int i = 0; i < inBitmap.Width; i++) {
                for (int j = 0; j < inBitmap.Height; j++)
                {
                    if (Point2D.point2dDistance(center.getX(), center.getY(), i, j) <= radious)
                    {

                        outBitmap.SetPixel(i, j, Color.FromArgb(inBitmap.GetPixel(i, j).ToArgb()));
                    }
                    else {
                        outBitmap.SetPixel(i, j, Color.Black);
                    }
                }
            }
            return outBitmap;
        }

        public static Color colorAverageAntiAliased(Bitmap inBitmap, double angle, Point2D finalPoint, Point2D center, Point2D offset) 
        {
            Point2D paux1 = Point2D.Point2DRotate(new Point2D(finalPoint.getX()-0.5F,finalPoint.getY()-0.5F), angle, center);
            Point2D paux2 = Point2D.Point2DRotate(new Point2D(finalPoint.getX() + 0.5F, finalPoint.getY() + 0.5F), angle, center);
            Point2D paux3 = Point2D.Point2DRotate(new Point2D(finalPoint.getX() - 0.5F, finalPoint.getY() + 0.5F), angle, center);
            Point2D paux4 = Point2D.Point2DRotate(new Point2D(finalPoint.getX() + 0.5F, finalPoint.getY() - 0.5F), angle, center);
                    

            paux1 = Point2D.detranslate(paux1,offset);
            paux2 = Point2D.detranslate(paux2,offset);
            paux3 = Point2D.detranslate(paux3, offset);
            paux4 = Point2D.detranslate(paux4, offset);

           
            System.Collections.ArrayList a = new System.Collections.ArrayList();
            a.Add(colorAverage(inBitmap, paux1));
            a.Add(colorAverage(inBitmap, paux2));
            a.Add(colorAverage(inBitmap, paux3));
            a.Add(colorAverage(inBitmap, paux4));

            return nColorAverage(a);
        }
       
        public static Color nColorAverage(ArrayList a) {
            double r=0, g=0, b=0;
            int n = a.Count;
            foreach (Color c in a) {
                r += c.R;
                g += c.G;
                b += c.B;
            }
            r /= n;
            g /= n;
            b /= n;
            return Color.FromArgb((int)(r + 0.5F), (int)(g + 0.5F), (int)(b + 0.5F));
        }

        public static Bitmap rotateImage3(Bitmap inBitmap, double angle, Point2D center) {
            //Parece ser que aforge implementa ya una forma de transformaciones afines...
            //la función en particular toma como parametros a,b,c,d,e,f, que son dentro de la matriz de transformacion
            //2D los siguientes coeficientes:
            //((a, b, 0, c),
            // (d, e, 0, f),
            // (0, 0, 1, 0),
            // (0, 0, 0, 1))

            //defino una transformación con la rotación inversa y una traslación al origen...
            Bitmap rotatedBitmap = new Bitmap(inBitmap.Width, inBitmap.Height);

            Point2D ulaux, llaux, uraux;
            //Sin AA{
            //Point2D paux2;
            //}

            Point2D offset = getOffset(inBitmap, -angle, center);

            ulaux = new Point2D(0, 0);
            llaux = new Point2D(0,inBitmap.Height);
            uraux = new Point2D(inBitmap.Width,0);
                  
            //ulaux = new Point2D(ulaux.getX()-centerx,ulaux.getY()-centery);
            ulaux = Point2D.Point2DRotate(ulaux, -angle, center);
            //ulaux = new Point2D(ulaux.getX()+centerx,ulaux.getY()+centery);
            ulaux = Point2D.detranslate(ulaux,offset);

            //llaux = new Point2D(llaux.getX() - centerx, llaux.getY() - centery);
            llaux = Point2D.Point2DRotate(llaux, -angle, center);
            //llaux = new Point2D(llaux.getX() + centerx, llaux.getY() + centery);
            llaux = Point2D.detranslate(llaux, offset);

            //uraux = new Point2D(uraux.getX() - centerx, uraux.getY() - centery);
            uraux = Point2D.Point2DRotate(uraux, -angle, center);
            //uraux = new Point2D(uraux.getX() + centerx, uraux.getY() + centery);
            uraux = Point2D.detranslate(uraux, offset);
            
            
            //g.DrawImage(inBitmap,new Point(0,0));
            PointF ulCorner = new PointF((float)ulaux.getX(), (float)ulaux.getY());
            PointF urCorner = new PointF((float)uraux.getX(), (float)uraux.getY());
            PointF llCorner = new PointF((float)llaux.getX(), (float)llaux.getY());
            PointF[] destPara = {ulCorner, urCorner, llCorner};
            Bitmap outBitmap = new Bitmap(inBitmap.Width, inBitmap.Height);
            Graphics g = Graphics.FromImage(outBitmap);
            g.FillRectangle(Brushes.Black, 0, 0, inBitmap.Width, inBitmap.Height);
            g.DrawImage(inBitmap,destPara);

            //return outBitmap;
            return inBitmap;
        }
    }
}
